package fun.easycode.webterminal.service.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ssh.ChannelType;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.extra.ssh.Sftp;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpProgressMonitor;
import fun.easycode.webterminal.common.contract.R;
import fun.easycode.webterminal.common.exception.SftpFileModifiedException;
import fun.easycode.webterminal.common.jsch.JschGroupQuery;
import fun.easycode.webterminal.common.jsch.JschJumpSession;
import fun.easycode.webterminal.common.jsch.JschJumpSessionPool;
import fun.easycode.webterminal.common.jsch.JschPasswdQuery;
import fun.easycode.webterminal.common.util.DateUtil;
import fun.easycode.webterminal.common.util.JacksonUtil;
import fun.easycode.webterminal.service.SftpService;
import fun.easycode.webterminal.service.dto.SftpDTO;
import fun.easycode.webterminal.service.dto.SftpFileContentDTO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Consumer;

@Slf4j
@Service
public class SftpServiceImpl implements SftpService {

//    @Resource
//    private MachineRepository machineRepository;

    @Override
    @SneakyThrows
    public R<SftpDTO> ls(String id, String path) {

        JschJumpSession session = JschJumpSessionPool.INSTANCE.get(id);

        try (Sftp sftp = JschUtil.createSftp(session.getSession())) {

            JschPasswdQuery passwdQuery = new JschPasswdQuery(sftp);
            JschGroupQuery groupQuery = new JschGroupQuery(sftp);

            List<ChannelSftp.LsEntry> lsEntries = sftp.lsEntries(path);

            SftpDTO dto = new SftpDTO();
            List<SftpDTO.File> files = new ArrayList<>();

            for (ChannelSftp.LsEntry lsEntry : lsEntries) {
                SftpDTO.File file = new SftpDTO.File();

                file.setName(lsEntry.getFilename());
                file.setType(lsEntry.getAttrs().isDir() ? "文件夹" : "文件");
                file.setPermission(lsEntry.getAttrs().getPermissionsString());
                file.setSize(lsEntry.getAttrs().isDir() ? "" : DataSizeUtil.format(lsEntry.getAttrs().getSize()));

//                System.err.println(lsEntry.getAttrs().getMTime());
                LocalDateTime localDateTime = DateUtil.asLocalDateTime(lsEntry.getAttrs().getMTime() * 1000L);
//                System.err.println(localDateTime);

                String lastModified = localDateTime.format(DateTimeFormatter.ofPattern(DateUtil.FORMAT_STR5));

                file.setLastModified(lastModified);

                String owner = passwdQuery.getUserName(lsEntry.getAttrs().getUId()) + "/" + groupQuery.getGroupName(lsEntry.getAttrs().getGId());
                file.setOwner(owner);
                files.add(file);
            }

            files.sort((o1, o2) -> {
                if (Objects.equals(o1.getType(), o2.getType())) {
                    return 0;
                } else if ("文件夹".equals(o1.getType())) {
                    return -1;
                } else {
                    return 1;
                }
            });

            dto.setFiles(files);
            dto.setPath(path);
            return R.success(dto);
        }
    }


    /**
     * 获取 SFTP 文件内容和元数据
     *
     * @param id   SFTP 连接 ID
     * @param path 文件路径
     * @return R<SftpFileContentDTO>
     */
    @Override
    @SneakyThrows
    public SftpFileContentDTO getFileContent(String id, String path) {
        JschJumpSession session = JschJumpSessionPool.INSTANCE.get(id);

        try (Sftp sftp = JschUtil.createSftp(session.getSession());
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {

            sftp.get(path, outputStream);
            String content = outputStream.toString(StandardCharsets.UTF_8);

            // 获取文件最后修改时间
            SftpATTRS attrs = sftp.lsEntries(path).get(0).getAttrs();
            long lastModified = attrs.getMTime() * 1000L; // JSch 返回的是秒，转换为毫秒

            SftpFileContentDTO dto = new SftpFileContentDTO();
            dto.setContent(content);
            dto.setLastModified(lastModified);

            return dto;
        } catch (Exception e) {
            log.error("获取 SFTP 文件内容失败: id={}, path={}", id, path, e);
            throw new RuntimeException("获取文件内容失败: " + e.getMessage(), e);
        }
    }

    /**
     * 保存 SFTP 文件内容（带并发修改校验）
     *
     * @param id           SFTP 连接 ID
     * @param path         文件路径
     * @param content      文件内容
     * @param lastModified 客户端获取文件时的最后修改时间 (毫秒)
     * @return R<Void>
     */
    @SneakyThrows
    public R<Void> saveFileContent(String id, String path, String content, Long lastModified) {
        JschJumpSession session = JschJumpSessionPool.INSTANCE.get(id);

        try (Sftp sftp = JschUtil.createSftp(session.getSession())) {
            // 1. 校验文件是否已被修改
            List<ChannelSftp.LsEntry> lsEntries = sftp.lsEntries(path);
            if (lsEntries.isEmpty()) {
                return R.error("文件不存在");
            }
            long currentLastModified = lsEntries.get(0).getAttrs().getMTime() * 1000L;
            if (currentLastModified != lastModified) {
                log.warn("SFTP 文件在编辑期间被修改: id={}, path={}, clientLastModified={}, serverLastModified={}",
                        id, path, lastModified, currentLastModified);
                throw new SftpFileModifiedException("文件在您编辑期间已被其他用户修改，请刷新后重试。");
            }

            // 2. 保存文件内容
            try (InputStream inputStream = IoUtil.toStream(content, StandardCharsets.UTF_8)) {
                sftp.put(inputStream, path, null, Sftp.Mode.OVERWRITE);
            }

            return R.message("保存成功");
        } catch (SftpFileModifiedException e) {
            // 重新抛出，由全局异常处理器处理
            throw e;
        } catch (Exception e) {
            log.error("保存 SFTP 文件内容失败: id={}, path={}", id, path, e);
            return R.error("保存文件内容失败: " + e.getMessage());
        }
    }

    @Override
    public R<?> upload(String id, String path, String fileName, InputStream stream) {

        JschJumpSession session = JschJumpSessionPool.INSTANCE.get(id);

        try (Sftp sftp = JschUtil.createSftp(session.getSession())) {

            String filePath = StrUtil.addSuffixIfNot(path, StrUtil.SLASH) + StrUtil.removePrefix(fileName, StrUtil.SLASH);

            // 传输监控
            SftpProgressMonitor monitor = new SftpProgressMonitor() {
                private long transferred;

                @Override
                public void init(int op, String src, String dest, long max) {
                    System.out.println("传输开始" + max);
                }

                @Override
                public boolean count(long count) {
                    transferred = transferred + count;
                    // System.out.println("当前传输的总大小: " + transferred + " bytes");
                    System.out.println("当前传输的总大小: " + transferred / (1024 * 1024) + " MB");
                    return true;
                }

                @Override
                public void end() {
                    System.out.println("传输完成");
                }
            };

            sftp.put(stream, filePath, monitor, Sftp.Mode.RESUME);

            return R.message("上传成功");
        }
    }

    @SneakyThrows
    @Override
    @Async
    public void uploadSSE(String id, String path, String fileName, MultipartFile file, SseEmitter emitter) {
        JschJumpSession session = JschJumpSessionPool.INSTANCE.get(id);

        ChannelSftp sftp =  ( ChannelSftp)session.getSession().openChannel(ChannelType.SFTP.getValue());

        try{

            sftp.connect();

            String filePath = StrUtil.addSuffixIfNot(path, StrUtil.SLASH) + StrUtil.removePrefix(fileName, StrUtil.SLASH);

            // 传输监控
            SftpProgressMonitor monitor = new SftpProgressMonitor() {
                private long transferred;
                private long max;

                @Override
                public void init(int op, String src, String dest, long max) {
                    log.info("传输开始 op = {}, src = {}, dest = {}, max = {} file size = {} .", op, src, dest, max, file.getSize());
                    this.max = file.getSize();
                }

                @Override
                public boolean count(long count) {
                    transferred = transferred + count;

                    Map<String,Object> data = new HashMap<>();
                    data.put("transferred", DataSizeUtil.format(transferred));
                    data.put("max", DataSizeUtil.format(max));
                    data.put("percent", transferred * 100 / max);
                    data.put("success", false);


                    SseEmitter.SseEventBuilder event = SseEmitter.event()
                            .data(JacksonUtil.toJson(data))
                            .id(path)
                            .name("message");

                    try {
                        emitter.send(event);
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }

                    return true;
                }

                @Override
                public void end() {

                    Map<String,Object> data = new HashMap<>();
                    data.put("transferred", DataSizeUtil.format(max));
                    data.put("max", DataSizeUtil.format(max));
                    data.put("percent", 100);
                    data.put("success", true);

                    SseEmitter.SseEventBuilder event = SseEmitter.event()
                            .data(JacksonUtil.toJson(data))
                            .id(path)
                            .name("message");

                    try {
                        emitter.send(event);
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }
                    emitter.complete();
                    log.info("传输完成");
                }
            };

            sftp.put(file.getInputStream(), filePath, monitor, Sftp.Mode.RESUME.ordinal());
        }finally {
            sftp.disconnect();
        }

    }

    @Override
    public R<?> delete(String id, String path) {

        JschJumpSession session = JschJumpSessionPool.INSTANCE.get(id);

        try (Sftp sftp = JschUtil.createSftp(session.getSession())) {

            if(sftp.isDir( path)){
                sftp.delDir(path);
            }else{
                sftp.delFile(path);
            }
            
            return R.success("删除成功");
        } catch (Exception e) {
            log.error("删除文件/文件夹失败: id={}, path={}", id, path, e);
            return R.error("删除失败: " + e.getMessage());
        }
    }

    @Override
    public void download(String id, String path, OutputStream stream, Consumer<Long> sizeConsumer) {

        JschJumpSession session = JschJumpSessionPool.INSTANCE.get(id);

        try (Sftp sftp = JschUtil.createSftp(session.getSession())) {

            List<ChannelSftp.LsEntry> lsEntries = sftp.lsEntries(path);
            if(!lsEntries.isEmpty() && sizeConsumer != null){
                sizeConsumer.accept(lsEntries.get(0).getAttrs().getSize());
            }

            sftp.get(path, stream);
        }
    }

}
